using System;
using System.IO;
using System.Windows.Forms;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration.Install;
using System.Data;
using System.Data.SqlClient;
using System.Net;
using System.Reflection;
using System.Xml;
using System.EnterpriseServices.Internal;

[RunInstaller(true)]
public class CustomInstaller : System.Configuration.Install.Installer
{
    private static Installer.wsEvents.Events events = new Installer.wsEvents.Events();
    private const string CONFIG_PATH = @"C:\Program Files\Microsoft Office Servers\12.0\Bin\";
    private const string CONFIG_FILE = "Microsoft.Office.Project.Server.Eventing.exe.config";
    private const string SERVICE_CONFIG_FILE = "TSAutoStatus.exe.config";
    private const string EVENT_HANDLER_ASSEMBLY = "TimesheetEvent.dll";

    public CustomInstaller() : base()
    {
    }
   
    public override void Install(IDictionary savedState)
    {
        try
        {
            // Get the parameters.
            string dbServer = Context.Parameters["DBSERVER"];
            string dbName = Context.Parameters["DBNAME"];
            string url = Context.Parameters["URL"];
            string sspName = Context.Parameters["SSPNAME"];

            // Setup DB objects.
            if (dbServer.Trim().Length > 0 && dbName.Trim().Length > 0)
            {
                ExecuteDBCommand(dbServer, dbName, "CreateDBObjects.sql");
            }

            // register the assembly in GAC
            RegisterAssemblyInGAC();

            // Register the event handlers.
            StringCollection eventUids = null;
            eventUids = RegisterEventHandler(url);
            // Update the events service config with the config params for the handler.
            UpdateEventsConfig(dbServer, dbName);

            // Update the timesheet service config with the config params.
            UpdateServiceConfig(dbServer, dbName, url, sspName);

            // Save the parameters for the uninstall.
            savedState.Add("DBSERVER", dbServer);
            savedState.Add("DBNAME", dbName);
            savedState.Add("URL", url);
            savedState.Add("EVENTUIDS", eventUids);

            base.Install(savedState);
        }
        catch (Exception ex)
        {
            throw new InstallException(ex.Message);
        }
    }
   
    public override void Uninstall(IDictionary savedState)
    {
        try
        {
            // unregister the assembly from GAC
            UnRegisterAssemblyInGAC();

            string dbServer = savedState["DBSERVER"].ToString();
            string dbName = savedState["DBNAME"].ToString();

            if (dbServer.Trim().Length > 0 && dbName.Trim().Length > 0)
            {
                ExecuteDBCommand(dbServer, dbName, "DropDBObjects.sql");
            }

            RemoveEventHandlers(savedState["URL"].ToString(), (StringCollection)savedState["EVENTUIDS"]);

            base.Uninstall(savedState);
        }
        catch (Exception ex)
        {
            throw new InstallException(ex.Message);
        }
    }

    private void RegisterAssemblyInGAC()
    {
        string assem = GetInstallDir() + "\\" + EVENT_HANDLER_ASSEMBLY;
        Publish p = new Publish();

        p.GacInstall(assem);

    }

    private void UnRegisterAssemblyInGAC()
    {
        string assem = GetInstallDir() + "\\" + EVENT_HANDLER_ASSEMBLY;
        Publish p = new Publish();

        p.GacRemove(assem);

    }
    private void ExecuteDBCommand(string dbServer, string dbName, string fileName)
    {
        const string CONN_STR = "Data Source={0};Initial Catalog={1};Integrated Security=True";

        string createFile = GetInstallDir() + "\\" + fileName;

        // Open the sql file and execute to build DB objects.
        string cmdText = "";
        using (StreamReader sr = new StreamReader(createFile))
        {
            String line;
            while ((line = sr.ReadLine()) != null)
            {
                cmdText += line;
                cmdText += "\n";
            }
        }

        try
        {
            using (SqlConnection cn = new SqlConnection(string.Format(CONN_STR, dbServer, dbName)))
            {
                cn.Open();
                SqlCommand cmd = new SqlCommand(cmdText, cn);
                cmd.CommandType = CommandType.Text;
                cmd.ExecuteNonQuery();
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error running database scripts.\n" + ex.Message);
        }
    }
    
    private StringCollection RegisterEventHandler(string url)
    {
        try
        {
            // Set the url for the events web service.
            if (!url.EndsWith("/")) url = url + "/";
            events.Url = url + "_vti_bin/psi/events.asmx";
            events.Credentials = CredentialCache.DefaultCredentials;

            // Get the event info from the assembly.
            string assem = GetInstallDir() + "\\TimesheetEvent.dll";
            Assembly eventAssem = Assembly.LoadFile(assem);

            Installer.wsEvents.EventHandlersDataSet eventHandlers = new Installer.wsEvents.EventHandlersDataSet();

            StringCollection eventUids = new StringCollection();
            foreach (Type t in eventAssem.GetTypes())
            {
                // Make sure it is an event receiver class.
                if (t.IsClass && t.BaseType.Module.Name == "Microsoft.Office.Project.Server.Events.Receivers.dll")
                {
                    foreach (MethodInfo mi in t.GetMethods())
                    {
                        if (mi.DeclaringType.FullName == t.FullName)
                        {
                            Installer.wsEvents.EventHandlersDataSet.EventHandlersRow row = eventHandlers.EventHandlers.NewEventHandlersRow();
                            row.AssemblyName = eventAssem.FullName;
                            row.ClassName = t.FullName;
                            row.Description = "Added by installer.";
                            row.EventHandlerUid = Guid.NewGuid();
                            row.EventId = GetEventID(t.BaseType.Name, mi.Name);
                            row.Name = t.BaseType.Name + " " + mi.Name + " TSAutoStatus Handler";
                            row.Order = 1;
                            eventHandlers.EventHandlers.AddEventHandlersRow(row);
                            eventUids.Add(row.EventHandlerUid.ToString());
                        }
                    }
                }
            }
            events.CreateEventHandlerAssociations(eventHandlers);
            return eventUids;
        }
        catch (Exception ex)
        {
            MessageBox.Show("Event handler registration failed.\n" + ex.Message);
            return null;
        }
    }

    private void RemoveEventHandlers(string url, StringCollection eventUids)
    {
        if (eventUids == null) return;

        try
        {
            if (!url.EndsWith("/")) url = url + "/";
            events.Url = url + "_vti_bin/psi/events.asmx";
            events.Credentials = CredentialCache.DefaultCredentials;

            Guid[] arr = new Guid[eventUids.Count];
            int i = 0;
            foreach (string uid in eventUids)
            {
                arr[i++] = new Guid(uid);
            }
            events.DeleteEventHandlerAssociations(arr);
        }
        catch (Exception ex)
        {
            MessageBox.Show("Event handler removal failed.\n" + ex.Message);
        }
    }

    private void UpdateEventsConfig(string dbServer, string dbName)
    {
        try
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(CONFIG_PATH + CONFIG_FILE);
            XmlNode config = doc.SelectSingleNode("configuration");
            XmlNode appSettings = config.SelectSingleNode("appSettings");
            if (appSettings == null)
            {
                appSettings = config.AppendChild(doc.CreateNode(XmlNodeType.Element, "appSettings", ""));
            }
            XmlNodeList items = appSettings.SelectNodes("add");
            bool DBServerFound = false;
            bool DBNameFound = false;
            foreach (XmlNode item in items)
            {
                switch (item.Attributes["key"].Value)
                {
                    case "DBServer":
                        item.Attributes["value"].Value = dbServer;
                        DBServerFound = true;
                        break;

                    case "DBName":
                        item.Attributes["value"].Value = dbName;
                        DBNameFound = true;
                        break;
                }
            }
            if (!DBServerFound)
            {
                SetAttribute(doc, appSettings, "DBServer", dbServer);
            }
            if (!DBNameFound)
            {
                SetAttribute(doc, appSettings, "DBName", dbName);
            }
            doc.Save(CONFIG_PATH + CONFIG_FILE);
        }
        catch (Exception ex)
        {
            MessageBox.Show("UpdateEventsConfig failed.  " + ex.Message);
        }
    }

    private void UpdateServiceConfig(string dbServer, string dbName, string url, string ssp)
    {
        try
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(GetInstallDir() + @"\" + SERVICE_CONFIG_FILE);
            XmlNode config = doc.SelectSingleNode("configuration");
            XmlNode appSettings = config.SelectSingleNode("applicationSettings");
            XmlNode app = appSettings.SelectSingleNode("Microsoft.EPM.TSAutoStatus.Properties.Settings");
            XmlNodeList items = app.SelectNodes("setting");
            XmlNode itemValue;
            foreach (XmlNode item in items)
            {
                itemValue = item.SelectSingleNode("value");
                switch (item.Attributes["name"].Value)
                {
                    case "LogFolder":
                        itemValue.InnerText = GetInstallDir() + @"\Logs";
                        break;

                    case "LogTimesheetEvent":
                        itemValue.InnerText = "True";
                        break;

                    case "LogFileEveryHour":
                        itemValue.InnerText = "True";
                        break;

                    case "PWASSPName":
                        itemValue.InnerText = ssp;
                        break;

                    case "AutomaticallySubmitStatusUpdate":
                        itemValue.InnerText = "True";
                        break;

                    case "DatabaseServer":
                        itemValue.InnerText = dbServer;
                        break;

                    case "DatabaseName":
                        itemValue.InnerText = dbName;
                        break;

                    case "PSURL":
                        itemValue.InnerText = url;
                        break;
                }
            }
            doc.Save(GetInstallDir() + @"\" + SERVICE_CONFIG_FILE);
        }
        catch (Exception ex)
        {
            MessageBox.Show("UpdateServiceConfig failed.  " + ex.Message);
        }
    }

    private void SetAttribute(XmlDocument doc, XmlNode appSettings, string key, string value)
    {
        XmlNode node = appSettings.AppendChild(doc.CreateNode(XmlNodeType.Element, "add", ""));
        XmlAttribute attr = doc.CreateAttribute("key");
        attr.Value = key;
        node.Attributes.Append(attr);
        XmlAttribute attr1 = doc.CreateAttribute("value");
        attr1.Value = value;
        node.Attributes.Append(attr1);
    }

    private string GetInstallDir()
    {
        string dir = Context.Parameters["AssemblyPath"];
        int index = dir.LastIndexOf('\\');
        if (index == -1) throw new InstallException("Invalid installation path.");
        return dir.Substring(0, index);
    }

    private int GetEventID(string recClass, string eventMethod)
    {
        // Get all the events to filter.
        Installer.wsEvents.EventsDataSet dsEvents = events.ReadEventsList();

        int index = recClass.LastIndexOf("EventReceiver");
        string sourceName = recClass.Substring(0, index);
        string eventName = eventMethod.Substring(2);
        
        DataView dv = new DataView(dsEvents.Event);
        dv.RowFilter = dsEvents.Event.Columns["SourceName"].ColumnName + " = '" + sourceName + "'";
        dv.RowFilter = dv.RowFilter + " AND " + dsEvents.Event.Columns["EventName"].ColumnName + " = '" + eventName + "'";
        if (dv.Count == 0) return -1;
        DataRowView drv = dv[0];
        return (int)drv["EventId"];
    }
}
